﻿using System;
using System.Linq;
using System.Data;
using System.Text;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace Dapper.Test
{

    [AttributeUsage(AttributeTargets.Property)]
    public class KeyAttribute : Attribute
    {
    }

    public static class DapperExtensions
    {
        public static int Insert<T>(this IDbConnection connection, T entityvalue)
        {
            return Insert(connection, null, entityvalue, null);
        }
        public static int Insert<T>(this IDbConnection connection, T entityvalue, IDbTransaction tran)
        {
            return Insert(connection, null, entityvalue, tran);
        }
        public static int Insert<T>(this IDbConnection connection, string sql, T entityvalue, IDbTransaction tran)
        {
            var name = entityvalue.GetType().Name;
            var sb = new StringBuilder(sql);
            if (sql == null)
            {
                sb.AppendFormat("insert into {0}", name);
                sb.Append(" (");
                BuildInsertParameters(entityvalue, sb);
                sb.Append(") values (");
                BuildInsertValues(entityvalue, sb);
                sb.Append(")");

            }
            return connection.Execute(sb.ToString(), entityvalue, tran);
        }

        public static int Update<T>(this IDbConnection connection, T entityvalue)
        {
            return Update(connection, null, entityvalue,null);
        }
        public static int Update<T>(this IDbConnection connection, T entityvalue,IDbTransaction tran)
        {
            return Update(connection, null, entityvalue, tran);
        }
        public static int Update<T>(this IDbConnection connection, string sql, T entityvalue,IDbTransaction tran)
        {
            var idProps = GetIdProperties(entityvalue);
            if (idProps.Count() == 0)
                throw new ArgumentException("Entity must have at least one [Key] property");

            var name = entityvalue.GetType().Name;

            var sb = new StringBuilder(sql);
            if (sql == null)
                sb.AppendFormat("update {0}", name);

            sb.AppendFormat(" set ");
            BuildUpdateSet(entityvalue, sb);
            sb.Append(" where ");
            BuildWhere(sb, idProps.ToArray());

            return connection.Execute(sb.ToString(), entityvalue,tran);
        }

        public static int Delete<T>(this IDbConnection connection, T entityvalue)
        {
            return Delete(connection, null, entityvalue,null);
        }

        public static int Delete<T>(this IDbConnection connection, T entityvalue,IDbTransaction tran)
        {
            return Delete(connection, null, entityvalue,tran);
        }

        public static int Delete<T>(this IDbConnection connection, string sql, T entityvalue, IDbTransaction tran)
        {
            var idProps = typeof(T).IsAnonymousType() ?
                GetAllProperties(entityvalue) :
                GetIdProperties(entityvalue);

            if (idProps.Count() == 0)
                throw new ArgumentException("Entity must have at least one [Key] property");

            var name = entityvalue.GetType().Name;

            var sb = new StringBuilder(sql);
            if (sql == null)
                sb.AppendFormat("delete from {0}", name);

            sb.Append(" where ");
            BuildWhere(sb, idProps);

            return connection.Execute(sb.ToString(), entityvalue,tran);
        }

        private static void BuildUpdateSet(object entityToUpdate, StringBuilder sb)
        {
            var nonIdProps = GetNonIdProperties(entityToUpdate).ToArray();

            for (var i = 0; i < nonIdProps.Length; i++)
            {
                var property = nonIdProps[i];

                sb.AppendFormat("{0} = @{1}", property.Name, property.Name);
                if (i < nonIdProps.Length - 1)
                    sb.AppendFormat(", ");
            }
        }

        private static void BuildWhere(StringBuilder sb, IEnumerable<PropertyInfo> idProps)
        {
            for (var i = 0; i < idProps.Count(); i++)
            {
                sb.AppendFormat("{0} = @{1}", idProps.ElementAt(i).Name, idProps.ElementAt(i).Name);
                if (i < idProps.Count() - 1)
                    sb.AppendFormat(" and ");
            }
        }

        private static void BuildInsertValues(object entityToInsert, StringBuilder sb)
        {
            var props = GetAllProperties(entityToInsert);

            for (var i = 0; i < props.Count(); i++)
            {
                var property = props.ElementAt(i);
                if (property.GetCustomAttributes(true).Where(a => a is KeyAttribute).Any()) continue;
                sb.AppendFormat("@{0}", property.Name);
                if (i < props.Count() - 1)
                    sb.Append(", ");
            }
        }

        private static void BuildInsertParameters(object entityToInsert, StringBuilder sb)
        {
            var props = GetAllProperties(entityToInsert);

            for (var i = 0; i < props.Count(); i++)
            {
                var property = props.ElementAt(i);
                if (property.GetCustomAttributes(true).Where(a => a is KeyAttribute).Any()) continue;
                sb.Append(property.Name);
                if (i < props.Count() - 1)
                    sb.Append(", ");
            }
        }

        private static IEnumerable<PropertyInfo> GetAllProperties(object entity)
        {
            return entity.GetType().GetProperties();
        }

        private static IEnumerable<PropertyInfo> GetNonIdProperties(object entity)
        {
            return GetAllProperties(entity).Where(p => p.GetCustomAttributes(true).Any(a => a is KeyAttribute) == false);
        }

        private static IEnumerable<PropertyInfo> GetIdProperties(object entity)
        {
            return GetAllProperties(entity).Where(p => p.GetCustomAttributes(true).Any(a => a is KeyAttribute));
        }
    }

    public static class TypeExtension
    {
        public static Boolean IsAnonymousType(this Type type)
        {
            if (type == null) return false;

            var hasCompilerGeneratedAttribute = type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Count() > 0;
            var nameContainsAnonymousType = type.FullName.Contains("AnonymousType");
            var isAnonymousType = hasCompilerGeneratedAttribute && nameContainsAnonymousType;

            return isAnonymousType;
        }
    }
}